package com.ipan.builder.ognl;

import java.util.Map;

import ognl.Ognl;
import ognl.OgnlContext;
import ognl.OgnlException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.ipan.builder.ognl.accessor.CompoundRootAccessor;
import com.ipan.builder.ognl.accessor.SecurityMemberAccess;
import com.ipan.builder.util.CompoundRoot;

/**
 * OGNL值栈实现类
 * 
 * @author iPan
 * @version 2013-11-30
 */
public class OgnlValueStack implements ValueStack {
	public static final String THROW_EXCEPTION_ON_FAILURE = OgnlValueStack.class.getName() + ".throwExceptionOnFailure";
	public static final CompoundRootAccessor COMPOUNDROOT_ACCESSOR = new CompoundRootAccessor();
	private CompoundRoot root;
    private Map<String, Object> context;
    private SecurityMemberAccess securityMemberAccess;
    private Logger logger = LoggerFactory.getLogger(getClass());
    
	protected OgnlValueStack() {
    	setRoot(COMPOUNDROOT_ACCESSOR, new CompoundRoot(), null, false);
    }
    
	protected OgnlValueStack(CompoundRoot root, Map<String, Object> vsContext, boolean allowStaticMethodAccess) {
		setRoot(COMPOUNDROOT_ACCESSOR, root, vsContext, allowStaticMethodAccess);
    }
    
	protected OgnlValueStack(ValueStack vs, Map<String, Object> context, boolean allowStaticMethodAccess) {
		setRoot(COMPOUNDROOT_ACCESSOR, new CompoundRoot(vs.getRoot()), context, allowStaticMethodAccess);
    }
	
	protected void setRoot(CompoundRootAccessor accessor, CompoundRoot compoundRoot, Map<String, Object> vsContext, 
			boolean allowStaticMethodAccess) {
		this.root = compoundRoot;
		this.securityMemberAccess = new SecurityMemberAccess(allowStaticMethodAccess);
		this.context = Ognl.createDefaultContext(this.root, accessor, null, securityMemberAccess);
		if (vsContext != null) {
			((OgnlContext) this.context).setValues(vsContext);
		}
		context.put(VALUE_STACK, this);
		Ognl.setClassResolver(context, accessor);
		((OgnlContext) context).setTraceEvaluations(false);
		((OgnlContext) context).setKeepLastEvaluation(false);
	}

	public Map<String, Object> getContext() {
		return context;
	}

	public CompoundRoot getRoot() {
		return root;
	}

	public void setValue(String expr, Object value) {
		setValue(expr, value, false);
	}

	public void setValue(String expr, Object value, boolean throwExceptionOnFailure) {
		Map<String, Object> context = getContext();
        try {
            Ognl.setValue(expr, context, root, value);
        } catch (OgnlException e) {
        	handleOgnlException(expr, value, throwExceptionOnFailure, e);
        }
	}

	public String findString(String expr) {
		return findString(expr, false);
	}

	public String findString(String expr, boolean throwExceptionOnFailure) {
		return (String) findValue(expr, String.class, throwExceptionOnFailure);
	}

	public Object findValue(String expr) {
		return findValue(expr, false);
	}

	public Object findValue(String expr, boolean throwExceptionOnFailure) {
		if (expr == null) {
			return null;
		}
		
		Object value = null;
		try {
            value = getValueUsingOgnl(expr);
            if (value == null) {
            	value = findInContext(expr);
            }
        } catch (OgnlException e) {
            handleOgnlException(expr, throwExceptionOnFailure, e);
        }
		return value;
	}
	
	public Object findValue(String expr, Class<?> asType) {
		return findValue(expr, asType, false);
	}

	public Object findValue(String expr, Class<?> asType, boolean throwExceptionOnFailure) {
		if (expr == null) {
			return null;
		}
		
		Object value = null;
		try {
            value = getValueUsingOgnl(expr, asType);
            if (value == null) {
            	value = findInContext(expr);
            }
        } catch (OgnlException e) {
            handleOgnlException(expr, throwExceptionOnFailure, e);
        }
		return value;
	}

	public Object peek() {
		return root.peek();
	}

	public Object pop() {
		return root.pop();
	}

	public void push(Object o) {
		root.push(o);
	}

	public int size() {
		return root.size();
	}

	private Object getValueUsingOgnl(String expr) throws OgnlException {
		return Ognl.getValue(expr, context, root);
    }
	
	private Object getValueUsingOgnl(String expr, Class<?> asType) throws OgnlException {
		return Ognl.getValue(expr, context, root, asType);
    }
	
	private Object findInContext(String name) {
        return getContext().get(name);
    }
	
	private void handleOgnlException(String expr, Object value, boolean throwExceptionOnFailure, OgnlException e) {
        String msg = "Error setting expression '" + expr + "' with value '" + value + "'";
        logger.error(msg);
        if (throwExceptionOnFailure) {
            throw new RuntimeException(msg, e);
        }
    }
	
	private void handleOgnlException(String expr, boolean throwExceptionOnFailure, OgnlException e) {
		String msg = "Error getting expression '" + expr + "'";
		logger.error(msg);
        if (throwExceptionOnFailure) {
        	throw new RuntimeException(msg, e);
        }
    }
	
}
